Imports Microsoft.VisualBasic
Imports System
Imports System.Drawing
Imports Phoenix.Tools.Win32

Namespace Phoenix.Tools.General
    ''' <summary>
    ''' Summary description for ColorConvert.
    ''' </summary>
    Public Class ColorUtil
#Region "Class static members"
        Private Shared backgroundColor As Color = Color.Empty
        Private Shared selectionColor As Color = Color.Empty
        Private Shared controlColor As Color = Color.Empty
        Private Shared pressedColor As Color = Color.Empty
        Private Shared checkedColor As Color = Color.Empty
        Private Shared borderColor As Color = Color.Empty

        Private Shared m_brushBack As SolidBrush
        Private Shared m_brushSelect As SolidBrush
        Private Shared m_brushCtrl As SolidBrush
        Private Shared m_brushPress As SolidBrush
        Private Shared m_brushCheck As SolidBrush
        Private Shared m_brushBorder As SolidBrush

        Private Shared m_penBack As Pen
        Private Shared m_penSelect As Pen
        Private Shared m_penCtrl As Pen
        Private Shared m_penPress As Pen
        Private Shared m_penCheck As Pen
        Private Shared m_penBorder As Pen

        Private Shared useCustomColor As Boolean = False
#End Region

#Region "Initialization/Constructor"
        ' No need to construct this object
        Private Sub New()
        End Sub


        Public Shared ReadOnly Property UsingCustomColor() As Boolean
            Get
                Return useCustomColor
            End Get
        End Property

#End Region

#Region "Systemcolors names"
        Public Shared SystemColorNames As String() = {"ActiveBorder", "ActiveCaption", "ActiveCaptionText", "AppWorkspace", "Control", "ControlDark", "ControlDarkDark", "ControlLight", "ControlLightLight", "ControlText", "Desktop", "GrayText", "HighLight", "HighLightText", "HotTrack", "InactiveBorder", "InactiveCaption", "InactiveCaptionText", "Info", "InfoText", "Menu", "MenuText", "ScrollBar", "Window", "WindowFrame", "WindowText"}
#End Region

#Region "Conversion between RGB and Hue, Saturation and Luminosity function helpers"
        Public Shared Sub HSLToRGB(ByVal h As Single, ByVal s As Single, ByVal l As Single, ByRef r As Single, ByRef g As Single, ByRef b As Single)
            ' given h,s,l,[240 and r,g,b [0-255]
            ' convert h [0-360], s,l,r,g,b [0-1]
            h = (h / 240) * 360
            s /= 240
            l /= 240
            r /= 255
            g /= 255
            b /= 255

            ' Begin Foley
            Dim m1, m2 As Single

            ' Calc m2
            If l <= 0.5F Then
                'm2=(l*(l+s)); seems to be typo in Foley??, replace l for 1
                m2 = (l * (1 + s))
            Else
                m2 = (l + s - l * s)
            End If

            'calc m1
            m1 = 2.0F * l - m2

            'calc r,g,b in [0-1]
            If s = 0.0F Then
                ' leave out the UNDEFINED part, h will always have value ' Achromatic: There is no hue
                'TODO: INSTANT VB TODO TASK: Assignments within expressions are not supported in VB.NET
                'TODO: INSTANT VB TODO TASK: Assignments within expressions are not supported in VB.NET
                r = l
                g = l
                b = l
            Else
                r = getRGBValue(m1, m2, h + 120.0F)
                g = getRGBValue(m1, m2, h)
                b = getRGBValue(m1, m2, h - 120.0F)
            End If

            ' End Foley
            ' convert to 0-255 ranges
            r *= 255
            g *= 255
            b *= 255

        End Sub

        Private Shared Function getRGBValue(ByVal n1 As Single, ByVal n2 As Single, ByVal hue As Single) As Single
            ' Helper function for the HSLToRGB function above
            If hue > 360.0F Then
                hue -= 360.0F
            ElseIf hue < 0.0F Then
                hue += 360.0F
            End If

            If hue < 60.0 Then
                Return n1 + (n2 - n1) * hue / 60.0F
            ElseIf hue < 180.0F Then
                Return n2
            ElseIf hue < 240.0F Then
                Return n1 + (n2 - n1) * (240.0F - hue) / 60.0F
            Else
                Return n1
            End If
        End Function

        Public Shared Sub RGBToHSL(ByVal r As Integer, ByVal g As Integer, ByVal b As Integer, ByRef h As Single, ByRef s As Single, ByRef l As Single)

            'Computer Graphics - Foley p.595
            Dim delta As Single
            Dim fr As Single = CSng(r) / 255
            Dim fg As Single = CSng(g) / 255
            Dim fb As Single = CSng(b) / 255
            Dim max As Single = Math.Max(fr, Math.Max(fg, fb))
            Dim min As Single = Math.Min(fr, Math.Min(fg, fb))

            'calc the lightness
            l = (max + min) / 2

            If max = min Then
                'should be undefined but this works for what we need
                s = 0
                h = 240.0F
            Else

                delta = max - min

                'calc the Saturation
                If l < 0.5 Then
                    s = delta / (max + min)
                Else
                    s = delta / (2.0F - (max + min))
                End If

                'calc the hue
                If fr = max Then
                    h = (fg - fb) / delta
                ElseIf fg = max Then
                    h = 2.0F + (fb - fr) / delta
                ElseIf fb = max Then
                    h = 4.0F + (fr - fg) / delta
                End If

                'convert hue to degrees
                h *= 60.0F
                If h < 0.0F Then
                    h += 360.0F
                End If
            End If
            'end foley

            'convert to 0-255 ranges
            'h [0-360], h,l [0-1]
            l *= 240
            s *= 240
            h = (h / 360) * 240

        End Sub
#End Region

#Region "Visual Studio .NET colors calculation helpers"
        Public Shared Property VSNetBackgroundColor() As Color
            Get
                If useCustomColor AndAlso Not backgroundColor.Equals(Color.Empty) Then
                    Return backgroundColor
                Else
                    Return CalculateColor(SystemColors.Window, SystemColors.Control, 220)
                End If
            End Get
            Set(ByVal Value As Color)
                ' Flag that we are going to use custom colors instead
                ' of calculating the color based on the system colors
                ' -- this is a way of hooking up into the VSNetColors that I use throughout
                ' the UtilityLibrary
                useCustomColor = True
                backgroundColor = Value
            End Set
        End Property

        Public Shared ReadOnly Property VSNetBackgroundBrush() As Brush
            Get
                ' if system colors changed by user then re-create brush
                If m_brushBack Is Nothing Then
                    m_brushBack = New SolidBrush(VSNetBackgroundColor)
                End If

                m_brushBack.Color = VSNetBackgroundColor

                Return m_brushBack
            End Get
        End Property

        Public Shared ReadOnly Property VSNetBackgroundPen() As Pen
            Get
                If m_penBack Is Nothing Then
                    m_penBack = New Pen(VSNetBackgroundBrush)
                End If

                m_penBack.Color = VSNetBackgroundColor

                Return m_penBack
            End Get
        End Property


        Public Shared Property VSNetSelectionColor() As Color
            Get
                If useCustomColor AndAlso Not selectionColor.Equals(Color.Empty) Then
                    Return selectionColor
                Else
                    Return CalculateColor(SystemColors.Highlight, SystemColors.Window, 70)
                End If
            End Get
            Set(ByVal Value As Color)
                ' Flag that we are going to use custom colors instead
                ' of calculating the color based on the system colors
                ' -- this is a way of hooking up into the VSNetColor that I use throughout
                ' the UtilityLibrary
                useCustomColor = True
                selectionColor = Value
            End Set
        End Property

        Public Shared ReadOnly Property VSNetSelectionBrush() As Brush
            Get
                If m_brushSelect Is Nothing Then
                    m_brushSelect = New SolidBrush(VSNetSelectionColor)
                End If

                m_brushSelect.Color = VSNetSelectionColor

                Return m_brushSelect
            End Get
        End Property
        Public Shared ReadOnly Property VSNetSelectionPen() As Pen
            Get
                If m_penSelect Is Nothing Then
                    m_penSelect = New Pen(VSNetSelectionBrush)
                End If

                m_penSelect.Color = VSNetSelectionColor

                Return m_penSelect
            End Get
        End Property


        Public Shared Property VSNetControlColor() As Color
            Get
                If useCustomColor AndAlso Not controlColor.Equals(Color.Empty) Then
                    Return controlColor
                Else
                    Return CalculateColor(SystemColors.Control, VSNetBackgroundColor, 195)
                End If
            End Get
            Set(ByVal Value As Color)
                ' Flag that we are going to use custom colors instead
                ' of calculating the color based on the system colors
                ' -- this is a way of hooking up into the VSNetColors that I use throughout
                ' the UtilityLibrary
                useCustomColor = True
                controlColor = Value
            End Set

        End Property

        Public Shared ReadOnly Property VSNetControlBrush() As Brush
            Get
                If m_brushCtrl Is Nothing Then
                    m_brushCtrl = New SolidBrush(VSNetControlColor)
                End If

                m_brushCtrl.Color = VSNetControlColor

                Return m_brushCtrl
            End Get
        End Property

        Public Shared ReadOnly Property VSNetControlPen() As Pen
            Get
                If m_penCtrl Is Nothing Then
                    m_penCtrl = New Pen(VSNetControlBrush)
                End If

                m_penCtrl.Color = VSNetControlColor

                Return m_penCtrl
            End Get
        End Property


        Public Shared Property VSNetPressedColor() As Color
            Get
                If useCustomColor AndAlso Not pressedColor.Equals(Color.Empty) Then
                    Return pressedColor
                Else
                    Return CalculateColor(SystemColors.Highlight, ColorUtil.VSNetSelectionColor, 70)
                End If
            End Get
            Set(ByVal Value As Color)
                ' Flag that we are going to use custom colors instead
                ' of calculating the color based on the system colors
                ' -- this is a way of hooking up into the VSNetColors that I use throughout
                ' the UtilityLibrary
                useCustomColor = True
                pressedColor = Value
            End Set
        End Property

        Public Shared ReadOnly Property VSNetPressedBrush() As Brush
            Get
                If m_brushPress Is Nothing Then
                    m_brushPress = New SolidBrush(VSNetPressedColor)
                End If

                m_brushPress.Color = VSNetPressedColor

                Return m_brushPress
            End Get
        End Property

        Public Shared ReadOnly Property VSNetPressedPen() As Pen
            Get
                If m_penPress Is Nothing Then
                    m_penPress = New Pen(VSNetPressedBrush)
                End If

                m_penPress.Color = VSNetPressedColor

                Return m_penPress
            End Get
        End Property


        Public Shared Property VSNetCheckedColor() As Color
            Get
                If useCustomColor AndAlso Not pressedColor.Equals(Color.Empty) Then
                    Return checkedColor
                Else
                    Return CalculateColor(SystemColors.Highlight, SystemColors.Window, 30)
                End If
            End Get
            Set(ByVal Value As Color)
                ' Flag that we are going to use custom colors instead
                ' of calculating the color based on the system colors
                ' -- this is a way of hooking up into the VSNetColors that I use throughout
                ' the UtilityLibrary
                useCustomColor = True
                checkedColor = Value
            End Set
        End Property

        Public Shared ReadOnly Property VSNetCheckedBrush() As Brush
            Get
                If m_brushCheck Is Nothing Then
                    m_brushCheck = New SolidBrush(VSNetCheckedColor)
                End If

                m_brushCheck.Color = VSNetCheckedColor

                Return m_brushCheck
            End Get
        End Property

        Public Shared ReadOnly Property VSNetCheckedPen() As Pen
            Get
                If m_penCheck Is Nothing Then
                    m_penCheck = New Pen(VSNetCheckedBrush)
                End If

                m_penCheck.Color = VSNetCheckedColor

                Return m_penCheck
            End Get
        End Property


        Public Shared Property VSNetBorderColor() As Color
            Get
                If useCustomColor AndAlso Not borderColor.Equals(Color.Empty) Then
                    Return borderColor
                Else
                    ' This color is the default color unless we are using
                    ' custom colors
                    Return SystemColors.Highlight
                End If
            End Get
            Set(ByVal Value As Color)
                ' Flag that we are going to use custom colors instead
                ' of calculating the color based on the system colors
                ' -- this is a way of hooking up into the VSNetColors that I use throughout
                ' the UtilityLibrary
                useCustomColor = True
                borderColor = Value
            End Set
        End Property

        Public Shared ReadOnly Property VSNetBorderBrush() As Brush
            Get
                If m_brushBorder Is Nothing Then
                    m_brushBorder = New SolidBrush(VSNetBorderColor)
                End If

                m_brushBorder.Color = VSNetBorderColor

                Return m_brushBorder
            End Get
        End Property

        Public Shared ReadOnly Property VSNetBorderPen() As Pen
            Get
                If m_penBorder Is Nothing Then
                    m_penBorder = New Pen(VSNetBorderBrush)
                End If

                m_penBorder.Color = VSNetBorderColor

                Return m_penBorder
            End Get
        End Property


        Private Shared Function CalculateColor(ByVal front As Color, ByVal back As Color, ByVal alpha As Integer) As Color

            ' Use alpha blending to brigthen the colors but don't use it
            ' directly. Instead derive an opaque color that we can use.
            ' -- if we use a color with alpha blending directly we won't be able
            ' to paint over whatever color was in the background and there
            ' would be shadows of that color showing through
            Dim frontColor As Color = Color.FromArgb(255, front)
            Dim backColor As Color = Color.FromArgb(255, back)

            Dim frontRed As Single = frontColor.R
            Dim frontGreen As Single = frontColor.G
            Dim frontBlue As Single = frontColor.B
            Dim backRed As Single = backColor.R
            Dim backGreen As Single = backColor.G
            Dim backBlue As Single = backColor.B

            Dim fRed As Single = frontRed * alpha / 255 + backRed * (CSng(255 - alpha) / 255)
            Dim newRed As Byte = CByte(fRed)
            Dim fGreen As Single = frontGreen * alpha / 255 + backGreen * (CSng(255 - alpha) / 255)
            Dim newGreen As Byte = CByte(fGreen)
            Dim fBlue As Single = frontBlue * alpha / 255 + backBlue * (CSng(255 - alpha) / 255)
            Dim newBlue As Byte = CByte(fBlue)

            Return Color.FromArgb(255, newRed, newGreen, newBlue)

        End Function
#End Region

#Region "General functions"
        Public Shared Function ColorFromPoint(ByVal g As Graphics, ByVal x As Integer, ByVal y As Integer) As Color
            Dim hDC As IntPtr = g.GetHdc()
            ' Get the color of the pixel first
            Dim colorref As System.UInt32 = WindowsAPI.GetPixel(hDC, x, y)
            Dim Red As Byte = GetRValue(colorref)
            Dim Green As Byte = GetGValue(colorref)
            Dim Blue As Byte = GetBValue(colorref)
            g.ReleaseHdc(hDC)
            Return Color.FromArgb(Red, Green, Blue)
        End Function

        Public Shared Function IsKnownColor(ByVal color As Color, ByRef knownColor As Color, ByVal useTransparent As Boolean) As Boolean

            ' Using the Color structrure "FromKnowColor" does not work if
            ' we did not create the color as a known color to begin with
            ' we need to compare the rgbs of both color
            Dim currentColor As Color = Color.Empty
            Dim badColor As Boolean = False
            For enumValue As knownColor = 0 To knownColor.YellowGreen.ToKnownColor
                currentColor = color.FromKnownColor(enumValue)
                Dim colorName As String = currentColor.Name
                If (Not useTransparent) Then
                    badColor = (colorName = "Transparent")
                End If
                If color.A = currentColor.A AndAlso color.R = currentColor.R AndAlso color.G = currentColor.G AndAlso color.B = currentColor.B AndAlso (Not currentColor.IsSystemColor) AndAlso (Not badColor) Then
                    knownColor = currentColor
                    Return True
                End If

            Next enumValue
            Return False

        End Function

        Public Shared Function IsSystemColor(ByVal color As Color, ByRef knownColor As Color) As Boolean

            ' Using the Color structrure "FromKnowColor" does not work if
            ' we did not create the color as a known color to begin with
            ' we need to compare the rgbs of both color
            Dim currentColor As Color = Color.Empty
            For enumValue As knownColor = 0 To knownColor.YellowGreen.ToKnownColor
                currentColor = color.FromKnownColor(enumValue)
                Dim colorName As String = currentColor.Name
                If color.R = currentColor.R AndAlso color.G = currentColor.G AndAlso color.B = currentColor.B AndAlso currentColor.IsSystemColor Then
                    knownColor = currentColor
                    Return True
                End If

            Next enumValue
            Return False
        End Function

        Public Shared Function GetCOLORREF(ByVal color As Color) As System.UInt32
            Return RGB(color.R, color.G, color.B)
        End Function


        Public Shared Function ColorFromRGBString(ByVal text As String) As Color
            Dim rgbColor As Color = Color.Empty
            Dim RGBs As String() = text.Split(","c)
            If RGBs.Length <> 3 Then
                ' If we don't have three pieces of information, then the
                ' string is not properly formatted, inform the use
                Throw New Exception("RGB color string is not well formed")
            End If

            Dim stringR As String = RGBs(0)
            Dim stringG As String = RGBs(1)
            Dim stringB As String = RGBs(2)
            Dim R, G, B As Integer

            Try
                R = Convert.ToInt32(stringR)
                G = Convert.ToInt32(stringG)
                B = Convert.ToInt32(stringB)
                If (R < 0 OrElse R > 255) OrElse (G < 0 OrElse G > 255) OrElse (B < 0 OrElse B > 255) Then
                    Throw New Exception("Out of bounds RGB value")
                Else
                    ' Convert to color
                    rgbColor = Color.FromArgb(R, G, B)
                    ' See if we have either a web color or a systgem color
                    Dim knownColor As Color = Color.Empty
                    Dim isKnown As Boolean = ColorUtil.IsKnownColor(rgbColor, knownColor, True)
                    If (Not isKnown) Then
                        isKnown = ColorUtil.IsSystemColor(rgbColor, knownColor)
                    End If
                    If isKnown Then
                        rgbColor = knownColor
                    End If
                End If
            Catch e1 As InvalidCastException
                Throw New Exception("Invalid RGB value")
            End Try

            Return rgbColor
        End Function
#End Region


#Region "Windows RGB related macros"
        Public Shared Function GetRValue(ByVal color As System.UInt32) As Byte
            Return Convert.ToByte(color)
        End Function

        Public Shared Function GetGValue(ByVal color As System.UInt32) As Byte
            Return (CByte((Convert.ToInt16(color)) >> 8))
        End Function

        Public Shared Function GetBValue(ByVal color As System.UInt32) As Byte
            Return (Convert.ToByte(Convert.ToInt32(color) >> 16))
        End Function

        Public Shared Function RGB(ByVal r As Integer, ByVal g As Integer, ByVal b As Integer) As System.UInt32
            Return (Convert.ToUInt32((CByte(r) Or (CShort(CByte(g)) << 8)) Or ((CShort(CByte(b))) << 16)))
        End Function
        Public Shared Function RGB(ByVal clr As Color) As System.UInt32
            Return (Convert.ToUInt32((CByte(clr.R) Or (CShort(CByte(clr.G)) << 8)) Or ((CShort(CByte(clr.B))) << 16)))
        End Function
#End Region
    End Class
End Namespace
